#!/usr/bin/env python
# -*- coding: utf-8 -*-

import climax
import logging
import mox
import re
import StringIO
import sys
import unittest

CliParser = climax.CliParser


class TestCliParser(unittest.TestCase):
    def setUp(self):
        self.Eql = self.assertEqual
        self.Neq = self.assertNotEqual
        self.Rai = self.assertRaises
        self.Tru = self.assertTrue
        self.Fal = self.assertFalse
    def tearDown(self):
        pass

    def test_IndexNotationAndDotNotation(self):
        config = (CliParser().Take('--foo').As(int)
            .Parse(['', '--foo=50']))
        self.assertEqual(50, config['foo'])
        self.assertEqual(50, config.foo)

    def test_BasicConversion(self):
        self.Eql(50, (CliParser().Take('-i').As(int)
            .Parse(['', '-i50']).i))
        self.Eql(90, (CliParser().Take('-j').As(int)
            .Parse(['', '-j', '90']).j))
        self.Eql(70.0, (CliParser().Take('--foo').As(float)
            .Parse(['', '--foo=70']).foo))
        self.Eql(70.0, (CliParser().Take('--foo').As(float)
            .Parse(['', '--foo', '70']).foo))
        self.Eql('bar', (CliParser().Take('-i').As(str)
            .Parse(['', '-i', 'bar']).i))
        self.Eql(u'88', (CliParser().Take('--foo').As(unicode)
            .Parse(['', '--foo', '88']).foo))
        parser = CliParser().Take('--foo').As(unicode)
        self.Eql(u'\u4e2d\u6587',
                parser.Parse(['', '--foo', '\xe4\xb8\xad\xe6\x96\x87']).foo)
        self.Eql(u'\u4e2d\u6587',
                parser.Parse(['', '--foo', u'\u4e2d\u6587']).foo)
        self.Eql(True, (CliParser().Take('--foo').As(bool)
            .Parse(['', '--foo']).foo))
        self.Eql(True, (CliParser().Take('-b').As(bool)
            .Parse(['', '-b']).b))

    def test_ShortOptionPrefixContainsLongOptionPrefix(self):
        self.Rai(ValueError, CliParser,
                 long_option_prefix='--', short_option_prefix='--/')

    def test_Alias(self):
        parser = CliParser().Take('--foo').Alias('-f').As(int)
        self.assertEqual(70, parser.Parse(['', '--foo=70']).foo)
        self.assertEqual(50, parser.Parse(['', '-f', '50']).foo)
        parser = CliParser().Take('--bar').As(float).Alias('-b')
        self.assertEqual(70.0, parser.Parse(['', '--bar=70']).bar)
        self.assertEqual(50.0, parser.Parse(['', '-b', '50']).bar)

    def test_MultiAliases(self):
        parser = CliParser().Take('--foo').Alias('-f').Alias('-o').As(int)
        self.assertEqual(70, parser.Parse(['', '--foo=70']).foo)
        self.assertEqual(50, parser.Parse(['', '-f', '50']).foo)
        self.assertEqual(30, parser.Parse(['', '-o', '30']).foo)
        parser = CliParser().Take('--bar').As(float).Alias('-b')
        self.assertEqual(70.0, parser.Parse(['', '--bar=70']).bar)
        self.assertEqual(50.0, parser.Parse(['', '-b', '50']).bar)

    def test_Arguments(self):
        config = (CliParser().Take('-i').As(int)
            .Parse(['', 'arg1', '-i50', 'arg2']))
        self.assertEqual(2, len(config))
        self.Rai(IndexError, config.__getitem__, 2)
        self.assertEqual('arg1', config[0])
        self.assertEqual('arg2', config[1])
        self.assertEqual(50, config['i'])
        self.assertEqual(50, config.i)
        args = ['arg1', 'arg2']
        i = 0
        for value in config:
            self.Eql(args[i], value)
            i += 1

    def test_DefaultValue(self):
        config = CliParser().Take('-i').WithDefault(30).Parse([''])
        self.assertEqual(30, config['i'])
        config = (CliParser().Take('-b').WithDefault(False)
            .Parse(['']))
        self.assertEqual(False, config['b'])

    def test_SupportedDefaultValueType(self):
        for value in [3, 430892409L, 3.7, 'hello, world!', u'中文']:
            self.assertEqual(value, CliParser().Take('-i').WithDefault(
                value).Parse(['', '-i%s' % value]).i)
        self.assertEqual(True, (CliParser()
            .Take('-i').WithDefault(False).Parse(['', '-i']).i))
        self.assertEqual(False, (CliParser()
            .Take('-i').WithDefault(True).Parse(['', '-i']).i))

    def test_MultipleOptions(self):
        config = (CliParser().Take('-i').WithDefault(5)
            .Take('--foo').As(float)
            .Parse(['', '--foo=-7.2', '-i', '20']))
        self.assertEqual(20, config.i)
        self.assertEqual(-7.2, config.foo)

    def test_MapBy(self):
        square = lambda x: x*x
        config = (CliParser().Take('-i').As(int).MapBy(square)
            .Parse(['', '-i', '20']))
        self.assertEqual(400, config.i)

    def test_Validation(self):
        # missing WithDefault() or As() raises
        self.Rai(climax.OptionNotValidError,
                 CliParser().Take('-i').Will('Set foo').Parse,
                 [''])

    def test_InvalidKeyForConfig(self):
        config = CliParser().Take('-i').WithDefault(5).Parse([''])
        self.Eql(5, config['i'])
        self.Rai(KeyError, config.__getitem__, 7.5)

    def test_UnusedOptions(self):
        config = (CliParser().Take('-i').WithDefault(5)
            .Take('--foo').As(float).Parse(['']))
        self.assertEqual(5, config.i)
        self.assertFalse(hasattr(config, 'foo'))

    def test_MissingValue(self):
        self.Rai(ValueError, CliParser().Take('-i').As(int).Parse,
            ['', '-i'])
        self.Rai(ValueError, CliParser().Take('--foo').As(int).Parse,
            ['', '--foo'])

    def test_OverwriteOption(self):
        self.Rai(ValueError,
                 (CliParser().Take('-i').As(int)
                    .Take('-j').As(float).Take),
                 '-i')
        self.Rai(ValueError,
                 (CliParser().Take('-i').As(int)
                    .Take('-j').As(float).Alias),
                 '-i')

    def test_OverwriteType(self):
        config = (CliParser().Take('-i').As(int).As(float)
            .Parse(['', '-i30']))
        self.assertEqual(30.0, config.i)

    def test_NotAType(self):
        self.Rai(TypeError, CliParser().Take('-i').As, 'float')

    def test_ConflictTypeAndDefaultValue(self):
        self.Rai(TypeError,
                 CliParser().Take('-i').As(float).WithDefault,
                 30)
        self.Rai(TypeError,
                 CliParser().Take('-i').WithDefault(2).As,
                 float)

    def test_DefaultValueInconsistentWithCliValue(self):
        parser = CliParser().Take('-i').WithDefault(3.0)
        self.Rai(ValueError, parser.Parse, ['', '-ikitty'])

    def test_UnknownOptionName(self):
        self.Rai(ValueError, CliParser().Take('--foo').As(float).Parse,
                 ['', '-i50'])

    def test_ConversionError(self):
        self.Rai(ValueError, CliParser().Take('-i').As(int).Parse,
                 ['', '-ibb'])

    def test_NotStartingWithTake(self):
        self.Rai(climax.CalledBeforeTakeError, CliParser().As, int)
        self.Rai(climax.CalledBeforeTakeError,
                 CliParser().WithDefault, 'yoyo')
        self.Rai(climax.CalledBeforeTakeError, CliParser().Alias, 'yoyo')
        self.Rai(climax.CalledBeforeTakeError, CliParser().For, 'yoyo')
        self.Rai(climax.CalledBeforeTakeError, CliParser().Will, 'yoyo')
        self.Rai(climax.CalledBeforeTakeError, CliParser().Argument, 2)
        self.Rai(climax.CalledBeforeTakeError, CliParser().MapBy,
                 lambda x: x)

    def test_ChangeOptionPrefix(self):
        parser = CliParser(long_option_prefix='/').Take('/foo').As(int)
        self.Eql(30, parser.Parse(['', '/foo=30']).foo)
        parser = CliParser(short_option_prefix='=').Take('=f').As(int)
        self.Eql(30, parser.Parse(['', '=f30']).f)
        config = (CliParser(short_option_prefix='=')
            .Take('=f').As(int).Take('--foo').As(int)
            .Parse(['', '--foo=5', '=f', '7']))
        self.Eql(5, config.foo)
        self.Eql(7, config.f)

    def test_TokenWithOldPrefixBecomesArgument(self):
        config = (CliParser(short_option_prefix='/')
            .Take('/f').As(int).Parse(['', '-f30']))
        self.Eql(1, len(config))
        self.Eql('-f30', config[0])
        self.Rai(KeyError, config.__getitem__, 'f')

    def test_ConverterValueType(self):
        def MyConv(value_string):
            enum_map = {'GOOD': 0, 'BAD': 1, 'UGLY': 2}
            if enum_map.has_key(value_string):
                return value_string
            else:
                raise ValueError('%s is not a known value' % value_string)
        parser = CliParser().Take('--foo').As(MyConv)
        self.Eql('UGLY', parser.Parse(['', '--foo=UGLY']).foo)
        self.Rai(ValueError, parser.Parse, ['', '--foo=WORSE'])
        # conversion function is not equal to a value.
        self.Rai(TypeError, CliParser().Take('--foo').WithDefault(MyConv('GOOD')).As,
                 MyConv)
        class MyEnum(object):
            ENUM_TUPLE = ('GOOD', 'BAD', 'UGLY')
            def __init__(self, value_string):
                if value_string in self.ENUM_TUPLE:
                    self.value = value_string
                else:
                    raise ValueError('%s is not a known value' % value_string)
        parser = CliParser().Take('--foo').As(MyEnum)
        self.Eql('UGLY', parser.Parse(['', '--foo=UGLY']).foo.value)
        self.Rai(ValueError, parser.Parse, ['', '--foo=WORSE'])
        parser = CliParser().Take('--foo').As(MyEnum).WithDefault(MyEnum('BAD'))
        self.Eql('UGLY', parser.Parse(['', '--foo=UGLY']).foo.value)
        self.Eql('BAD', parser.Parse(['']).foo.value)

    def test_ConfigStringRepresentation(self):
        config = (CliParser().Take('-f').As(int)
            .Take('bar').As(float).Argument().For('bar score')
            .Parse(['', '-f3', '1.0']))
        data = str(config)
        self.Tru('f=3' in data)
        self.Tru('1.0' in data)

    def test_ClassEnumValueType(self):
        class Foo(object):
            def __init__(self, value_string):
                enum_map = {'GOOD': 0, 'BAD': 1, 'UGLY': 2}
                if enum_map.has_key(value_string):
                    self.value = enum_map[value_string]
                else:
                    raise ValueError('%s is not a known value' % value_string)
        parser = CliParser().Take('--foo').As(Foo)
        self.Eql(2, parser.Parse(['', '--foo=UGLY']).foo.value)
        self.Rai(ValueError, parser.Parse, ['', '--foo=WORSE'])

    def test_ClassMultiValueType(self):
        class Foo(object):
            def __init__(self, value_string):
                values = value_string.split(',')
                if len(values) != 2:
                    raise ValueError('%s should be a,b' % value_string)
                self.a, self.b = values[0], values[1]
        parser = CliParser().Take('--foo').As(Foo)
        foo = parser.Parse(['', '--foo=30,2']).foo
        self.Eql('30', foo.a)
        self.Eql('2', foo.b)
        self.Rai(ValueError, parser.Parse, ['', '--foo=WORSE'])

    def test_GenerateHelp(self):
        parser = CliParser().Take('--foo').As(bool)
        help_message = parser.GetHelpMessage()
        self.Tru('--foo' in help_message)
        self.Tru('--foo=' not in help_message)
        self.Tru('[arguments]' in help_message)

    def test_NamedArgument(self):
        kFilename = '/tmp/named.dat'
        parser = (CliParser().Take('file').Argument().Is('File to process.'))
        config = parser.Parse(['', kFilename])
        help_message = parser.GetHelpMessage()
        self.Tru('file' in help_message)
        self.Eql(kFilename, config[0])
        self.Eql(kFilename, config['file'])
        self.Rai(ValueError, parser.Parse, [''])  # missing mandatory argument

    def test_NamedArgumentWithDefaultValue(self):
        parser = (CliParser().Take('xfile').Argument().WithDefault('-')
            .Is('File to process.'))
        config = parser.Parse([''])
        self.Eql(['-'], config['xfile'])

    def test_NamedArgumentWithType(self):
        parser = (CliParser().Take('num').As(int).Argument().WithDefault(1)
            .Is('Number to use.'))
        self.Eql([1], parser.Parse([''])['num'])
        self.Eql([1], parser.Parse(['']).num)
        self.Eql(5, parser.Parse(['', '5'])['num'])
        self.Eql(5, parser.Parse(['', '5'])[0])

    def test_NamedArgumentWithPlusMultiplicity(self):
        kFilename = ['/tmp/named2.dat', '/tmp/plus.dat']
        parser = (CliParser().Take('--foo').As(bool).Will('Enable foo.')
            .Take('file').Argument('+').Is('File(s) to process.'))
        config = parser.Parse(['', kFilename[0], kFilename[1]])
        help_message = parser.GetHelpMessage()
        self.Tru('file' in help_message)
        self.Eql(kFilename[0], config[0])
        self.Eql(kFilename[1], config[1])
        self.Eql(kFilename, config['file'])
        self.Rai(ValueError, parser.Parse, [''])  # missing argument

    def test_NamedArgumentWithStarMultiplicity(self):
        kFilename = ['/tmp/named3.dat', '/tmp/star.dat']
        parser = (CliParser().Take('--foo').As(bool).Will('Enable foo.')
            .Take('file').Argument('*').Is('File(s) to process.'))
        config = parser.Parse(['', kFilename[0], kFilename[1]])
        help_message = parser.GetHelpMessage()
        self.Tru('file' in help_message)
        self.Eql(kFilename[0], config[0])
        self.Eql(kFilename[1], config[1])
        self.Eql(kFilename, config['file'])
        config2 = parser.Parse([''])
        self.Eql([], config2['file'])  # no missing argument

    def test_NamedArgumentWithQuestionMarkMultiplicity(self):
        kFilename = ['/tmp/named4.dat', '/tmp/qmark.dat']
        parser = (CliParser().Take('--foo').As(bool).Will('Enable foo.')
            .Take('file').Argument('?').Is('File to process.'))
        config = parser.Parse(['', kFilename[0], kFilename[1]])
        help_message = parser.GetHelpMessage()
        self.Tru('file' in help_message)
        self.Eql(kFilename[0], config[0])
        self.Eql(kFilename[1], config[1])
        self.Eql([kFilename[0]], config['file'])  # at most 1 argument
        config2 = parser.Parse([''])
        self.Eql([], config2['file'])  # no missing argument

    def test_MultipleNamedArguments(self):
        kFilename = ['/tmp/named5.dat', '/tmp/named.dat']
        parser = (CliParser()
            .Take('infile').Argument(1).Is('File to read.')
            .Take('oufile').Argument('?').Is('File to write'))
        config = parser.Parse(['', kFilename[0], kFilename[1]])
        help_message = parser.GetHelpMessage()
        self.Tru('infile' in help_message)
        self.Tru('oufile' in help_message)
        self.Eql(kFilename[0], config[0])
        self.Eql(kFilename[1], config[1])
        self.Eql(kFilename[0], config['infile'])
        self.Eql([kFilename[1]], config['oufile'])
        config2 = parser.Parse(['', kFilename[0]])
        self.Eql(kFilename[0], config2['infile'])
        self.Eql([], config2['oufile'])

    def test_NamedArgumentWithDefault(self):
        kFilename = ['/tmp/named6.dat', '/tmp/def.dat']
        parser = (CliParser()
            .Take('infile').WithDefault('-').Argument('?').Is('input file.')
            .Take('oufile').WithDefault('-').Argument('?').Is('output file.'))
        config = parser.Parse(['', kFilename[0], kFilename[1]])
        help_message = parser.GetHelpMessage()
        self.Tru('infile' in help_message)
        self.Tru('oufile' in help_message)
        self.Eql(kFilename[0], config[0])
        self.Eql(kFilename[1], config[1])
        self.Eql([kFilename[0]], config['infile'])
        self.Eql([kFilename[1]], config['oufile'])
        config2 = parser.Parse(['', kFilename[0]])
        self.Eql([kFilename[0]], config2['infile'])
        self.Eql(['-'], config2['oufile'])
        config2 = parser.Parse([''])
        self.Eql(['-'], config2['infile'])
        self.Eql(['-'], config2['oufile'])

    def test_UseParser(self):
        p1 = CliParser()
        p2 = object()
        self.Rai(TypeError, p1.Use, p2)

    def test_NoCylicUse(self):
        p1 = CliParser()
        p2 = CliParser()
        p3 = CliParser()
        p1.Use(p2)
        self.Rai(climax.CyclicUseError, p2.Use, p1)
        p2.Use(p3)
        self.Rai(climax.CyclicUseError, p3.Use, p1)

    def test_ParseWithDelegates(self):
        p1 = CliParser().Take('-x').As(int).Take('-y').As(int)
        p2 = CliParser().Take('-z').As(float)
        p1.Use(p2)
        config = p1.Parse(['', '-y20', '-z3.5', '-x', '7'])
        self.Eql(7, config.x)
        self.Eql(20, config.y)
        self.Eql(3.5, config.z)
        self.Rai(ValueError, p1.Parse, ['', '-w'])

    def test_DefaultsInUsedParser(self):
        p1 = (CliParser()
            .Take('-x').WithDefault(3).Take('-y').As(int))
        p2 = CliParser().Take('-z').As(float)
        p3 = CliParser().Take('-a').As(str)
        p4 = CliParser().Take('-b').As(bool)
        p1.Use(p2)
        p2.Use(p3)
        config = p1.Parse(['', '-y20', '-z3.5', '-akitty'])
        self.Eql(3, config.x)
        self.Eql(20, config.y)
        self.Eql(3.5, config.z)
        self.Eql('kitty', config.a)
        p4.Use(p2)  # triggers p2._HasUsed(p4) where p2 has non-zero delegates.
        self.Eql(3.5, p4.Parse(['', '-z3.5']).z)

    def test_DuplicateOptionInUsedParser(self):
        p1 = (CliParser().Take('-x').As(int)
            .Take('-y').WithDefault(3))
        p2 = (CliParser().Take('-z').As(float)
            .Take('-y').As(str))
        p1.Use(p2)
        config = p1.Parse(['', '-y20', '-z3.5'])
        self.Rai(KeyError, config.__getitem__, 'x')
        self.Eql(20, config.y)
        self.Eql(3.5, config.z)

    def test_NoAddHelp(self):
        mocker = mox.Mox()
        mock_exit = mocker.CreateMockAnything()
        mocker.StubOutWithMock(sys, 'exit', use_mock_anything=True)
        saved_stdout, sys.stdout = sys.stdout, StringIO.StringIO()
        sys.exit()  # expect that sys.exit() will be called.
        CliParser().Parse(['', '-h'])
        mocker.UnsetStubs()
        sys.stdout, saved_stdout = saved_stdout, sys.stdout
        self.Tru('-h' in saved_stdout.getvalue())
        self.Rai(ValueError, CliParser(add_help=False).Parse, ['', '-h'])

    def test_AnsiTerminalHelpFormatter(self):
        name = 'x coordinate'
        parser = (CliParser().Take('-x').WithDefault(3).For(name)
            .Take('-b').As(bool))
        message = parser.GetHelpMessage('ansi')
        CSI = '\x1b['
        self.Tru(CSI + '4m' + name + CSI + '0m' in message)
        self.Tru('-b' in message)
        self.Tru(' (default: 3)' in message)
        parser = CliParser().Take('-x').WithDefault(0)
        message = parser.GetHelpMessage('ansi')
        self.Tru(' (default: 0)' in message)

    def test_HelpOptionValue(self):
        # setup mocks
        mocker = mox.Mox()
        mock_exit = mocker.CreateMockAnything()
        mocker.StubOutWithMock(sys, 'exit', use_mock_anything=True)
        saved_stdout, sys.stdout = sys.stdout, StringIO.StringIO()
        sys.exit()  # expect that sys.exit() will be called.
        # run and verify
        CliParser().Parse(['', '-hplain'])
        self.Tru('-h' in sys.stdout.getvalue())
        self.Rai(ValueError, CliParser(add_help=False).Parse, ['', '-h'])
        self.Rai(ValueError, CliParser().Parse, ['', '-h', 'ansj'])
        self.Rai(ValueError, CliParser().Parse, ['', '-h', 'plain,blah'])
        # clean up
        mocker.UnsetStubs()
        sys.stdout, saved_stdout = saved_stdout, sys.stdout

    def test_HelpAlignValue(self):
        # setup mocks
        mocker = mox.Mox()
        mock_exit = mocker.CreateMockAnything()
        mocker.StubOutWithMock(sys, 'exit', use_mock_anything=True)
        saved_stdout, sys.stdout = sys.stdout, StringIO.StringIO()
        sys.exit()  # expect that sys.exit() will be called.
        # run and verify
        parser = (CliParser().Take('--foo').Alias('-f').As(int)
            .Take('-b').Alias('--bar').As(bool).Parse(['', '-hansi.both']))
        message = sys.stdout.getvalue()
        self.Tru(None != re.search('^\s*--foo', message, re.MULTILINE))
        self.Tru(None != re.search('^\s*-f', message, re.MULTILINE))
        self.Tru(None != re.search('^\s*--bar', message, re.MULTILINE))
        self.Tru(None != re.search('^\s*-b', message, re.MULTILINE))
        # clean up
        mocker.UnsetStubs()
        sys.stdout, saved_stdout = saved_stdout, sys.stdout

    def test_FormatterNeedsFormatMethod(self):
        parser = CliParser()
        foo = object()
        self.Rai(TypeError, parser.GetHelpMessage, foo)

    def test_MissingValueIsTreatedAsEmpty(self):
        parser = (CliParser().Take('-x').As(str)
            .Take('-y').As(int))
        self.Eql('', parser.Parse(['', '-x']).x)
        config = parser.Parse(['', '-x', '-y5'])
        self.Eql('', config.x)
        self.Eql(5, config.y)
        self.Rai(ValueError, parser.Parse, ['', '-y'])

    def test_ParseIncorrectValueType(self):
        parser = (CliParser().Take('-x').As(int)
            .Take('-y').As(float))
        self.Rai(ValueError, parser.Parse, ['', '-x3.5'])
        self.Rai(ValueError, parser.Parse, ['', '-yhello'])

    def test_BuildIncorrectType(self):
        self.Rai(TypeError, CliParser().Take('-x').As(int).For, 30)

    def test_AlignOptions(self):
        self.Rai(ValueError, climax.BaseHelpFormatter, None, align='BLAH')
        self.Rai(ValueError, climax.BaseHelpFormatter, None, align='ALIGN_BLAH')
        self.Rai(ValueError, climax.BaseHelpFormatter, None, align='SHORT')
        self.Rai(ValueError, climax.BaseHelpFormatter, None, align='ALIGN_SHORT')
        self.Neq(None, climax.BaseHelpFormatter(None, align='short'))
        self.Neq(None, climax.BaseHelpFormatter(None, align='both'))

    def test_AlignBoth(self):
        parser = (CliParser().Take('--foo').Alias('-f').As(int)
            .Take('-b').Alias('--bar').As(bool))
        # plain formatter
        message = parser.GetHelpMessage(climax.BaseHelpFormatter(
            climax._PlainComponentMaker(), align='both'))
        self.Tru(None != re.search('^\s*--foo', message, re.MULTILINE))
        self.Tru(None != re.search('^\s*-f', message, re.MULTILINE))
        self.Tru(None != re.search('^\s*--bar', message, re.MULTILINE))
        self.Tru(None != re.search('^\s*-b', message, re.MULTILINE))

    def test_AlignShort(self):
        parser = (CliParser().Take('--foo').Alias('-f').As(int)
            .Take('-b').Alias('--bar').As(bool)
            .Take('--baz').As(bool))
        # plain formmater aligning short options
        message = parser.GetHelpMessage(climax.BaseHelpFormatter(
            climax._PlainComponentMaker(), align='short'))
        match = re.search('^(\s*)-f', message, re.MULTILINE)
        self.Tru(None != match)
        pattern_space = '^\s{%d}' % len(match.group(1))
        # test that there are identical number of spaces in front.
        self.Tru(None != re.search(pattern_space+'-b', message, re.MULTILINE))
        self.Tru(None == re.search('^\s*--foo', message, re.MULTILINE))
        self.Tru(None == re.search('^\s*--bar', message, re.MULTILINE))
        # test that the correct space is reserved for short options.
        pattern_reserved_space = '^\s{%d}' % (len(match.group(1)) + 3)
        self.Tru(None != re.search(pattern_reserved_space+'--baz', message, re.MULTILINE))

    def test_SubCommand(self):
        foo_parser = (CliParser().Take('--x').WithDefault(0.0).For('x_coordinate')
            .Take('--y').WithDefault(0.0).For('y_coordinate'))
        bar_parser = (CliParser().Take('--i').WithDefault(3).For('i_index')
            .Take('--j').WithDefault(-1).For('j_index'))
        parser = (CliParser().Take('foo').Command(foo_parser)
            .Take('bar').Command(bar_parser))
        # foo subcommand
        config = parser.Parse(['', 'foo', '--x=3.0'])
        self.Tru(hasattr(config, 'foo'))
        self.Fal(hasattr(config, 'bar'))
        self.Eql(3.0, config.foo.x)
        self.Eql(0.0, config.foo.y)
        # bar subcommand
        self.Rai(ValueError, parser.Parse, ['', 'bar', '--x=3.0'])
        config = parser.Parse(['', 'bar', '--i=5'])
        self.Eql(5, config.bar.i)
        self.Eql(-1, config.bar.j)
        self.Fal(hasattr(config, 'foo'))
        # both commands: don't work.
        self.Rai(ValueError, parser.Parse, ['', 'bar', '--i=5', 'foo', '--x=3.0'])
        # check the help message
        message = parser.GetHelpMessage()
        self.Tru('\nCommands' in message)
        self.Tru(None != re.search('\n\s+foo', message, re.MULTILINE))
        self.Tru(None != re.search('\n\s+bar', message, re.MULTILINE))

if __name__ == '__main__':
    unittest.main()
